﻿// <copyright file="KinectPreview.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Controls
{
    #region

    using System;
    using System.ComponentModel.Composition;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    using KinectExtensions.Services;

    using Microsoft.Research.Kinect.Nui;

    #endregion

    /// <summary>
    ///   TODO: Update summary.
    /// </summary>
    public class KinectPreview : Control
    {
        #region Constants and Fields

        /// <summary>
        ///   The blu e_ idx.
        /// </summary>
        private const int BLUE_IDX = 0;

        /// <summary>
        ///   The gree n_ idx.
        /// </summary>
        private const int GREEN_IDX = 1;

        /// <summary>
        ///   The re d_ idx.
        /// </summary>
        private const int RED_IDX = 2;

        /// <summary>
        ///   The depth frame 32.
        /// </summary>
        private readonly byte[] depthFrame32 = new byte[320 * 240 * 4];

        /// <summary>
        ///   The depth.
        /// </summary>
        private Image depth;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes static members of the <see cref = "KinectPreview" /> class.
        /// </summary>
        static KinectPreview()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(KinectPreview), new FrameworkPropertyMetadata(typeof(KinectPreview)));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "KinectPreview" /> class.
        /// </summary>
        public KinectPreview()
        {
            ServiceLocator.Instance.SatisfyImportsOnce(this);

            try
            {
                if (this.KinectService != null && this.KinectService.KinectRuntime != null && this.KinectService.KinectRuntime.DepthStream != null)
                {
                    this.KinectService.KinectRuntime.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
                    this.KinectService.KinectRuntime.DepthFrameReady += this.KinectRuntime_DepthFrameReady;
                }
            }
            catch (InvalidOperationException)
            {
                MessageBox.Show("Failed to open stream. Please make sure to specify a supported image type and resolution.");
                return;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets KinectService.
        /// </summary>
        [Import]
        public KinectService KinectService { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        ///   The on apply template.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.depth = this.GetTemplateChild("depth") as Image;
        }

        #endregion

        #region Methods

        /// <summary>
        ///   The kinect runtime_ depth frame ready.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void KinectRuntime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            if (this.depth == null)
            {
                return;
            }

            PlanarImage Image = e.ImageFrame.Image;
            byte[] convertedDepthFrame = this.convertDepthFrame(Image.Bits);

            this.depth.Source = BitmapSource.Create(320, 240, 96, 96, PixelFormats.Bgr32, null, convertedDepthFrame, 320 * 4);
        }

        /// <summary>
        ///   The convert depth frame.
        /// </summary>
        /// <param name = "depthFrame16">
        ///   The depth frame 16.
        /// </param>
        /// <returns>
        /// </returns>
        private byte[] convertDepthFrame(byte[] depthFrame16)
        {
            for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < this.depthFrame32.Length; i16 += 2, i32 += 4)
            {
                int player = depthFrame16[i16] & 0x07;
                int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);

                // transform 13-bit depth information into an 8-bit intensity appropriate
                // for display (we disregard information in most significant bit)
                var intensity = (byte)(255 - (255 * realDepth / 0x0fff));

                this.depthFrame32[i32 + RED_IDX] = 0;
                this.depthFrame32[i32 + GREEN_IDX] = 0;
                this.depthFrame32[i32 + BLUE_IDX] = 0;

                // choose different display colors based on player
                switch (player)
                {
                    case 0:
                        this.depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
                        this.depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 2);
                        this.depthFrame32[i32 + BLUE_IDX] = (byte)(intensity / 2);
                        break;
                    case 1:
                        this.depthFrame32[i32 + RED_IDX] = intensity;
                        break;
                    case 2:
                        this.depthFrame32[i32 + GREEN_IDX] = intensity;
                        break;
                    case 3:
                        this.depthFrame32[i32 + RED_IDX] = (byte)(intensity / 4);
                        this.depthFrame32[i32 + GREEN_IDX] = intensity;
                        this.depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 4:
                        this.depthFrame32[i32 + RED_IDX] = intensity;
                        this.depthFrame32[i32 + GREEN_IDX] = intensity;
                        this.depthFrame32[i32 + BLUE_IDX] = (byte)(intensity / 4);
                        break;
                    case 5:
                        this.depthFrame32[i32 + RED_IDX] = intensity;
                        this.depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 4);
                        this.depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 6:
                        this.depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
                        this.depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 2);
                        this.depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 7:
                        this.depthFrame32[i32 + RED_IDX] = (byte)(255 - intensity);
                        this.depthFrame32[i32 + GREEN_IDX] = (byte)(255 - intensity);
                        this.depthFrame32[i32 + BLUE_IDX] = (byte)(255 - intensity);
                        break;
                }
            }

            return this.depthFrame32;
        }

        #endregion
    }
}